# -*- coding: utf-8 -*-


# cmap2owl -- Helper application to convert from concept maps to OWL ontologies
# Copyright (c) 2008-2013  Rodrigo Rizzi Starr
#  
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#  
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#  
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.


'''
Contains a set of rules to identify superclass/subclass relationships

@author: Rodrigo Rizzi Starr
@copyright: Copyright © 2008-2013 Rodrigo Rizzi Starr
@license: MIT License
@contact: rodrigo.starr@gmail.com
'''


from CMap2Prolog import assemblyQuery
from mapperRule import MapperRule
from mapperQuestion import MapperQuestion
from infoProvider import SimpleInfoProvider
from log import log
log.debug('Loaded...')


# Hack due to my poor prolog
# The following is probably the ugliest code I've ever made.....
def calculateUnifications(db, relations):
    '''From a set of relations with the same name, form groups connected by 
        similar class'''
    
    # Now we extend relations to consider the from and to concepts
    extendedRelations = []
    for relation in relations:
        result = list(db.query(assemblyQuery('fromSetRelation(%(R)s, FromSet),'
                                             'toSetRelation(%(R)s, ToSet)',
                                             {'R': relation})))
        if len(result) > 1:
            log.error('The query for relation %s returned more than one result'
                      % relation)
        elif len(result) == 0:
            log.error('The query for relation %s returned no result'
                      % relation)
        else:
            result = result[0]
            
            extendedRelations.append((set([relation]),
                                      set(result['FromSet']),
                                      set(result['ToSet']),
                                      ))
    
    newGroups = extendedRelations
    novelty = True
    while novelty:
        novelty = False
        oldGroups = newGroups
        shouldVisit = [True]*len(oldGroups)
        newGroups = []
        
        for n, group in enumerate(oldGroups):
            if shouldVisit[n]:
                for m, nextGroup in enumerate(oldGroups[n+1:]):
                    
                    # At least one of these elements are in common
                    # If true, we must keep looking
                    novelty = any(
                            len(x & y) > 0 for x, y in zip(group, nextGroup) )
                            
                    if novelty:
                        shouldVisit[m + n + 1] = False
                        group = tuple(x | y for x, y in zip(group, nextGroup))
                
                if group not in newGroups:
                    newGroups.append(group)
                
    return [group[0] for group in oldGroups if len(group[0]) > 1]


class UnificateSimilarRule(MapperRule):
    '''Detects if two relations are in fact the same'''
    
    def __init__(self, db):
        
        rules = [
                 'inferenceGroup(unificationRule, [unification(_, _)])',
                 
                 'unification(Label, RSet) :- '
                    'setof(R, (llabel(R, Label),'
                              'positive(relation(R))), RSet),'
                    'notSingleton(RSet)',
                 
                 # Consequences
                 'equals(Element, EqSet) :- '
                    'isTrue(unification(_, [Element|EqSet]))',
                    ]
         
        MapperRule.__init__(self, 'UnificateSimilar', db, rules)
          
    def getQuestions(self):
        '''Returns the question objects for this rule'''
        questions = []
        query = 'possible(unification(Label, Set))'
        unifications = self.db.query(query)
        
        for answer in list(unifications):
            groups = calculateUnifications(self.db, answer['Set'])
            
            for group in groups:
                questions.append(UnificateSimilarQuestion(
                      self.db,
                      {'Set': group},
                      'Do the highlighted words have all the same meaning?',
                      helpFile = 'unificateSimilar',
                      label = answer['Label'],
                  ))
            
        return questions
      
    def getInfoProvider(self):
        'Returns an info provider about classes and individuals'
        
        return SimpleInfoProvider(self.db,
                            'Rel = %s, '
                              '(inEquals(Rel); inEquals(Rel, _))',
                            'This relation has been unified with others with '
                                'the same name')

    
class UnificateSimilarQuestion(MapperQuestion):
    '''Asks if relations with the same name are in fact similar
    '''
    
    def __init__(self,
                 db,
                 data,
                 question = '',
                 query = '',
                 helpFile = 'basic',
                 noneCondition = '',
                 label = ''):

        MapperQuestion.__init__(self,
                                db,
                                data,
                                question,
                                query,
                                helpFile = helpFile,)
        self.qLabel = label
        
    def updateIds(self):
        '''Return a set of updated ids'''
        
        query = assemblyQuery('possible(unification(%s, Set))', self.qLabel)
        answer = list(self.db.query(query))

        if len(answer) != 1:
            self.ids =  set([])
            log.error("The answer for " + self.queryIds + 
                      " was %d results long. This is weird." % len(answer))
        else:
            answer = answer[0]
            
            groups = calculateUnifications(self.db, answer['Set'])
            
            for group in groups:
                if group <= self.data['Set']:
                    self.ids = self.data['Set']
                    break
     
    def updateSensitivity(self):
        'Recalculates the status when the question changes'
        
        if not self.buttonPressed:
            self.updateIds()
            if len(self.ids) == 0:
                self.setAsInsensitive()
            else:
                self.setAsSensitive()
        else:
            self.setAsInsensitive()
    
    def assertOnYes(self):
        'Asserts an updated set of elements'
        
        query = assemblyQuery(
                    'isTrue(unification(%(Label)s, %(Ids)s))', 
                    {'Label':self.qLabel,
                     'Ids':self.ids})
        return [query]
    
    def assertOnNo(self):
        'Creates a string representing a question'
        
        query = assemblyQuery(
                    'isFalse(unification(%(Label)s, %(Ids)s))', 
                    {'Label':self.qLabel,
                     'Ids':self.ids})
        return [query]
       
